﻿using System;
using System.Collections.Generic;
using Autofac;
using Cubicle.Core.Config;
using Cubicle.Core.DataContracts;
using Cubicle.Core.Interfaces;
using Cubicle.Core.Processors;
using Cubicle.Core.Shared;
using Cubicle.Excel.Config;
using Cubicle.Excel.Functions;
using Cubicle.Excel.Shared;
using ExcelDna.Integration;

namespace Cubicle.Excel.Hosting
{
    public class RemoteProxy : EngineProxy, IDisposable
    {
        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        public RemoteProxy(string alias, Processor<object> marshalQueue, string remoteAddress, IContainer container)
            : base(alias, marshalQueue)
        {
            _container = container;
            RemoteAddress = remoteAddress;
        }

        private IContainer _container;
        public string RemoteAddress { get; private set; }

        private void RunRemote(Action<IEngine> action)
        {
            using (var lifetime = _container.BeginLifetimeScope())
            {
                try
                {
                    var client = lifetime.Resolve<IEngine>();
                    action(client);
                }
                catch (Exception e)
                {
                    _logger.ErrorException(String.Format("Error executing remote command. Address = {0}", RemoteAddress), e);
                }
            }
        }

        private T RunRemote<T>(Func<IEngine, T> function, T failValue)
        {
            using (var lifetime = _container.BeginLifetimeScope())
            {
                try
                {
                    var client = lifetime.Resolve<IEngine>();
                    return function(client);
                }
                catch (Exception e)
                {
                    _logger.ErrorException(String.Format("Error executing remote command. Address = {0}", RemoteAddress), e);
                    return failValue;
                }
            }
        }

        public override string Address { get { return RemoteAddress; } }

        public override bool IsConnected()
        {
            try   { return Id != null; }
            catch { return false; }
        }

        public override string StartHost(HostingType type, bool enableMetaData, int port)
        {
            return "Cannot host a remote Engine.";
        }

        public override bool StopHost() { return false; }

        public override bool IsHosted
        {
            get
            {
                return RunRemote(x => x.IsHosted(), false);
            }
        }

        public override string HostAddress
        {
            get
            {
                return RunRemote(x => x.GetHostAddress(), null);
            }
        }

        public override object ProxyInvoke(
            string handle,
            string memberName,
            object[,] parameters,
            object objKey,
            object[] methodArgs = null,
            object[] classArgs = null)
        {
            var key = objKey == ExcelMissing.Value ? null : objKey.ToString();
            var inst = CustomHelpers.CreateInstruction(handle, memberName, parameters, key, methodArgs, classArgs);
            return RunRemote(x => x.Process(inst), null);
        }

        #region Implementation of IRepository

        public override void Clear()
        {
            RunRemote(x => x.Clear());
        }

        public override string[] List()
        {
            return RunRemote(x => x.List(), null);
        }

        public override string Rename(string handle, string newKey)
        {
            return RunRemote(x => x.Rename(handle, newKey), null);
        }

        public override IRObject Remove(string handle)
        {
            RunRemote(x => x.RemoveHandle(handle));
            return null;
        }

        public override bool Has(string handle)
        {
            return RunRemote(x => x.Has(handle), false);
        }

        public override string Set(string key, object item)
        {
            return RunRemote(x => x.Set(key, item), null);
        }

        public override object GetProxy(string handle)
        {
            object result = RunRemote(x => x.Get(handle), null);
            return ExcelHelpers.JaggedToDouble(result);
        }

        public override IRObject GetRObject(string handle)
        {
            return null;
        }

        #endregion

        #region Implementation of IEngine

        public override string Id
        {
            get
            {
                return RunRemote(x => x.GetId(), null);
            }
        }

        public override object Process(Instruction instruction)
        {
            object result = null;
            result = RunRemote(x => x.Process(instruction), null);
            return result == null
                       ? result
                       : ExcelHelpers.JaggedToDouble(result);
        }

        public override bool Build(ProcessorConfig config)
        {
            RunRemote(x => x.Build(config));
            return true;
        }

        #endregion

        #region IUserInterface

        public override string GetVersion()
        {
            return RunRemote(x => x.GetVersion(), "Error");
        }

        public override IEnumerable<AssemblyData> GetAssemblyDetails()
        {
            return RunRemote(x => x.GetAssemblyDetails(), null);
        }

        public override IEnumerable<MemberData> GetMembers(string target)
        {
            return RunRemote(x => x.GetMembers(target), null);
        }

        public override IEnumerable<TypeData> GetTypes(string target)
        {
            return RunRemote(x => x.GetTypes(target), null);
        }

        public override TypeData GetType(string target)
        {
            return RunRemote(x => x.GetTypeData(target), null);
        }

        public override IEnumerable<MethodData> GetMethods(string target)
        {
            return RunRemote(x => x.GetMethods(target), null);
        }

        public override IEnumerable<FieldData> GetFields(string target)
        {
            return RunRemote(x => x.GetFields(target), null);
        }

        public override IEnumerable<PropertyData> GetProperties(string target)
        {
            return RunRemote(x => x.GetProperties(target), null);
        }

        public override IEnumerable<ConstructorData> GetConstructors(string target)
        {
            return RunRemote(x => x.GetConstructors(target), null);
        }

        public override IEnumerable<ParameterData> GetParameterData(string target, string memberName, int offset)
        {
            return RunRemote(x => x.GetParameterData(target, memberName, offset), null);
        }

        #endregion

        #region Implementation of IInvocationEngine

        public override IEnumerable<string> GetLog(int i)
        {
            return RunRemote(x => x.GetLog(i), null);
        }

        #endregion

        #region Implementation of IScriptManager

        public override bool RunScript(byte[] rawScript)
        {
            // First run normal script
            var result = false;
            result = RunRemote(x => x.RunByteScript(rawScript), false);

            // If execution failed return
            if (!result) return false;

            // Only proceeed with binding if this is local and succeeded running
            var bindScript = Helpers.BinaryToXml<BindingScript>(rawScript);
            if (bindScript == null) return true;

            // Execute binding
            BindingFunctions.BindScript(bindScript, this);
            return result;
        }

        #endregion

        #region Assembly loading

        public override string LoadAssembly(byte[] assembly, byte[] symbols = null)
        {
            return RunRemote(x => x.LoadAssembly(assembly, symbols), null);
        }

        #endregion

        #region Implementation of IDisposable

        public void Dispose()
        {
            if (_container != null) _container.Dispose();
        }

        #endregion

    }
}